# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import datetime
import functools

from absl import flags

from coin.base.datetime_util import to_datetime
from coin.strategy.mm.subscription import SubscriptionRequest, FeedSubscriptionRequest

from coin.exchange.binance.kr_rest.product import BinanceProduct
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct
from coin.exchange.bitflyer_v1.kr_rest.futures_product import BitflyerFuturesProduct
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct
from coin.exchange.gdax.kr_rest.product import GdaxProduct
from coin.exchange.okex_futures.kr_rest.futures_product import OkexFuturesProduct

from coin.strategy.mm.simple_sim import result_util
from coin.strategy.mm.simple_sim.strategy.pass_unhedge_3 import PassUnhedgedSimStrategy

ref_product = None
ref_product_sub_req = None
ref_product_true_book_funcs = None
trade_product = None
trade_product_true_book_funcs = None
trade_product_sub_req = None
price_multiplier_1 = None
skip_list = set()

P_FLAGS = flags.FlagValues()
flags.DEFINE_string('skip_from_result_dir',
                    None,
                    'If specified, skip profiles already run in specified dirs.',
                    flag_values=P_FLAGS)


def prepare(args, ref_ts):
  global ref_product, ref_product_sub_req, ref_product_true_book_funcs
  global trade_product, trade_product_sub_req, trade_product_true_book_funcs
  global price_multiplier_1
  global skip_list

  args = P_FLAGS(['fake_program_name'] + args, known_only=True)[1:]

  if P_FLAGS.skip_from_result_dir:
    result_dirs = P_FLAGS.skip_from_result_dir.split(',')
    results = result_util.load_sim_result(result_dirs)
    skip_list = set([res['name'] for res in results if res['qty_sum'] > 0])

  trade_product = BitmexFuturesProduct.FromStr('BTC-USD.PERPETUAL')
  trade_product_sub_req = SubscriptionRequest('Futures', 'Bitmex')
  trade_product_true_book_funcs = ((lambda book: book.get_ask_price_by_qty(10000.)),
                                   (lambda book: book.get_bid_price_by_qty(10000.)))

  args = dict(enumerate(args))
  ref_exchange = args.get(0, 'Okex')
  if ref_exchange == 'Okex':
    ref_product = OkexFuturesProduct.FromStr('BTC-USD.QUARTER', current_datetime=ref_ts)
    ref_product_sub_req = SubscriptionRequest('Futures', 'Okex', 'v3')
    ref_product_true_book_funcs = ((lambda book: book.get_ask_price_by_qty(100.)),
                                   (lambda book: book.get_bid_price_by_qty(100.)))

  elif ref_exchange == 'Binance':
    ref_product = BinanceProduct.FromStr('BTC-USDT')
    ref_product_sub_req = SubscriptionRequest('Spot', 'Binance')
    ref_product_true_book_funcs = ((lambda book: book.get_ask_price_by_qty(1.)),
                                   (lambda book: book.get_bid_price_by_qty(1.)))

  elif ref_exchange == 'Bitflyer':
    ref_product = BitflyerFuturesProduct.FromStr('BTC-JPY.PERPETUAL')
    ref_product_sub_req = SubscriptionRequest('Futures', 'Bitflyer')
    ref_product_true_book_funcs = ((lambda book: book.get_ask_price_by_qty(1.)),
                                   (lambda book: book.get_bid_price_by_qty(1.)))
    price_multiplier_1 = 1 / 110.

  elif ref_exchange == 'Bitfinex':
    ref_product = BitfinexProduct.FromStr('BTC-USD')
    ref_product_sub_req = SubscriptionRequest('Spot', 'Bitfinex')
    ref_product_true_book_funcs = ((lambda book: book.get_ask_price_by_qty(1.)),
                                   (lambda book: book.get_bid_price_by_qty(1.)))

  elif ref_exchange == 'Gdax':
    ref_product = GdaxProduct.FromStr('BTC-USD')
    ref_product_sub_req = SubscriptionRequest('Spot', 'Gdax')
    ref_product_true_book_funcs = ((lambda book: book.get_ask_price_by_qty(1.)),
                                   (lambda book: book.get_bid_price_by_qty(1.)))

  else:
    raise ValueError('Unsupported: %s' % ref_exchange)


def get_feed_sub_request(from_ts):
  feed_sub_req = FeedSubscriptionRequest()
  feed_sub_req.add_products([ref_product], ref_product_sub_req)
  feed_sub_req.add_products([trade_product], trade_product_sub_req)
  return feed_sub_req


def get_machines():
  return ['feed-01.eu-west-1.aws']


def get_time_ranges():
  ranges = []
  cur_dt = datetime.datetime(2019, 7, 10, 0, 0, 0)
  end_dt = datetime.datetime(2019, 7, 15, 0, 0, 0)
  while cur_dt < end_dt:
    ranges.append((cur_dt, cur_dt + datetime.timedelta(hours=24)))
    cur_dt += datetime.timedelta(hours=24)
  return ranges


def linear_sell_edge(edge, close_edge, max_pos, pos):
  if pos <= 0.:
    return edge
  elif pos >= max_pos:
    return close_edge
  else:
    p = (pos / float(max_pos))
    return (p * close_edge + (1 - p) * edge)


def linear_buy_edge(edge, close_edge, max_pos, pos):
  if pos >= 0.:
    return edge
  elif pos <= -max_pos:
    return close_edge
  else:
    p = (-pos / float(max_pos))
    return (p * close_edge + (1 - p) * edge)


def get_strategy(from_ts, to_ts):
  NS_PER_SECOND = (10**9)
  NS_PER_MINUTE = 60 * NS_PER_SECOND

  strategy_list = []

  for basis_ma_window in [2, 5, 30, 60]:
    for edge_bp in [2, 5, 9, 11]:
      if basis_ma_window <= 10 and edge_bp > 9:
        continue

      for close_edge_bp in sorted(set([-1, 2, edge_bp])):
        if close_edge_bp > edge_bp:
          continue

        for agg_edge in [None]:  # [None, (edge_bp + 8.5) / 10000.]:
          for stack in [6, 12, 24]:
            lot_size = 10. / stack
            delay = 1.

            pricing_param = {
                'basis_ma_window': basis_ma_window * NS_PER_MINUTE,
                'tick': 0.5,
                'book_askt_func_1': ref_product_true_book_funcs[0],
                'book_bidt_func_1': ref_product_true_book_funcs[1],
                'book_askt_func_2': trade_product_true_book_funcs[0],
                'book_bidt_func_2': trade_product_true_book_funcs[1],
                'price_multiplier_1': price_multiplier_1
            }

            executor_param = {
                'lot_size': lot_size,
                'min_pos': -lot_size * stack,
                'max_pos': lot_size * stack,
                'maker_fee': -2.5 / 10000.,
                'taker_fee': 7.5 / 10000.,
                'execution_delay': delay * NS_PER_SECOND,
                'post_only': True,
                'trade_qty_func': (lambda trade: trade.qty / trade.price)
            }

            use_agg = 'without_agg'
            if agg_edge:
              use_agg = 'with_agg'
            name = '%02dm.%02dbp.%02dbp.%02dstack.%s.%s' % (basis_ma_window,
                                                            edge_bp,
                                                            close_edge_bp,
                                                            stack,
                                                            use_agg,
                                                            to_datetime(from_ts).strftime('%Y%m%d'))

            if name in skip_list:
              continue

            strategy = PassUnhedgedSimStrategy(trade_product,
                                               ref_product,
                                               functools.partial(linear_sell_edge,
                                                                 edge_bp / 10000.,
                                                                 close_edge_bp / 10000.,
                                                                 1.),
                                               functools.partial(linear_buy_edge,
                                                                 edge_bp / 10000.,
                                                                 close_edge_bp / 10000.,
                                                                 1.),
                                               pricing_param,
                                               executor_param,
                                               trade_after=basis_ma_window,
                                               name=name,
                                               agg_edge=agg_edge)
            #    feature_filepath=('out/feature.%s.csv' % name),
            #    fill_filepath=('out/fill.%s.csv' % name))
            strategy_list.append(strategy)

  return strategy_list


def get_strategy_result(strategy):
  return {'name': strategy.name, **strategy.get_summary()}


def aggregate_result(results):
  return result_util.aggregate_sim_result(results)
